package services

import (
	"bytes"
	"gitee.com/igolang/imoney/internal/app/system/entities"
	"gitee.com/igolang/imoney/internal/app/system/entities/constants"
	"gitee.com/igolang/imoney/internal/app/system/services/dict"
	"gitee.com/igolang/imoney/internal/app/system/tools"
	commonEntities "gitee.com/igolang/imoney/internal/common/entities"
	"gitee.com/igolang/imoney/internal/common/httpResp"
	commonTools "gitee.com/igolang/imoney/internal/common/tools"
	"gitee.com/igolang/imoney/internal/models/dao"
	"gitee.com/igolang/imoney/internal/models/model"
	"gitee.com/igolang/pkg/xgorm"
	"gitee.com/igolang/pkg/xtime"
	"gitee.com/igolang/pkg/xutils"
	"github.com/gin-gonic/gin"
	"github.com/pkg/errors"
	"github.com/spf13/cast"
	"gorm.io/gen"
	"net/http"
	"slices"
	"time"
)

var (
	User = &user{}
)

type user struct{}

func (s *user) List(ctx *gin.Context) (*commonEntities.ListResp, error) {
	var req entities.UserPageListReq
	if err := ctx.ShouldBind(&req); err != nil {
		return nil, err
	}
	conds := s.formatListConds(&req)
	total, err := dao.SysUser.Where(conds...).Count()
	if err != nil {
		return nil, err
	}
	result := &commonEntities.ListResp{
		Total: total,
		Rows:  make([]*model.SysUser, 0),
	}
	if total == 0 {
		return result, nil
	}
	type row struct {
		*model.SysUser
		Dept     *model.SysDept `json:"dept"`
		DeptName string         `json:"deptName,omitempty"`
		Leader   string         `json:"leader,omitempty"`
		Lock     bool           `json:"lock"`
	}
	var rows []*row
	err = dao.SysUser.Select(dao.SysUser.ALL, dao.SysDept.DeptName, dao.SysDept.Leader).
		LeftJoin(dao.SysDept, dao.SysDept.DeptID.EqCol(dao.SysUser.DeptID)).
		Where(conds...).Offset(req.Offset).Limit(req.PageSize).Order(dao.SysUser.UserID.Desc()).Scan(&rows)
	if err != nil {
		return nil, err
	}
	for _, v := range rows {
		v.Dept = &model.SysDept{
			DeptID:   v.DeptID,
			DeptName: v.DeptName,
			Leader:   v.Leader,
		}
		v.DeptName = ""
		v.Leader = ""
		v.Lock = s.isLocking(v.UserName) != nil
	}
	result.Rows = rows
	return result, nil
}

func (s *user) Export(ctx *gin.Context) (*bytes.Buffer, error) {
	var req entities.UserPageListReq
	if err := ctx.ShouldBind(&req); err != nil {
		return nil, err
	}
	conds := s.formatListConds(&req)
	header := []string{"用户序号", "登录名称", "用户名称", "用户邮箱", "手机号码", "用户性别", "帐号状态", "最后登录IP", "最后登录时间", "部门名称", "部门负责人"}
	data := [][]string{header}
	type row struct {
		*model.SysUser
		DeptName string `json:"deptName,omitempty"`
		Leader   string `json:"leader,omitempty"`
	}
	var rows []*row
	err := dao.SysUser.Select(dao.SysUser.ALL, dao.SysDept.DeptName, dao.SysDept.Leader).
		LeftJoin(dao.SysDept, dao.SysDept.DeptID.EqCol(dao.SysUser.DeptID)).
		Where(conds...).Order(dao.SysUser.UserID.Desc()).Scan(&rows)
	if err != nil {
		return nil, err
	}
	sexOptions, _ := dict.Get("sys_user_sex")
	statusOptions, _ := dict.Get("sys_normal_disable")
	for _, v := range rows {
		data = append(data, []string{
			cast.ToString(v.UserID),
			v.UserName, v.NickName, v.Email, v.Phonenumber,
			dict.LabelOptions(sexOptions, v.Sex), dict.LabelOptions(statusOptions, v.Status),
			v.LoginIP, v.LoginDate.String(),
			v.DeptName, v.Leader,
		})
	}
	fh := xutils.CreateExcelFileWithData(data)
	defer fh.Close()
	return fh.WriteToBuffer()
}

func (s *user) Detail(ctx *gin.Context) (map[string]any, error) {
	id := cast.ToInt64(ctx.Param("id"))
	if id == 0 || id == constants.SuperAdminId {
		return nil, httpResp.ErrorCode(httpResp.ErrCodeInvalidParams)
	}
	data, err := s.info(id)
	if err != nil {
		return nil, err
	}

	result, err := s.RoleAndPost(id)
	if err != nil {
		return nil, err
	}
	result["data"] = data
	return result, nil
}

func (s *user) AuthRoleList(ctx *gin.Context) (map[string]any, error) {
	id := cast.ToInt64(ctx.Param("id"))
	if id == 0 || id == constants.SuperAdminId {
		return nil, httpResp.ErrorCode(httpResp.ErrCodeInvalidParams)
	}
	info, err := s.info(id)
	if err != nil {
		return nil, err
	}
	roleRow, err := dao.SysRole.Where(dao.SysRole.RoleID.Neq(constants.SuperAdminId)).Find()
	if err != nil {
		return nil, err
	}
	data := map[string]any{
		"roles": roleRow,
		"user":  info,
	}

	return data, nil
}

func (s *user) AuthRole(ctx *gin.Context) error {
	var req struct {
		UserId  int64  `json:"userId" form:"userId"`
		RoleIds string `json:"roleIds" form:"roleIds"`
	}
	if err := ctx.ShouldBind(&req); err != nil {
		return err
	}
	if req.UserId == 0 || req.UserId == constants.SuperAdminId || req.RoleIds == "" {
		return httpResp.ErrorCode(httpResp.ErrCodeInvalidParams)
	}
	roleIds := commonTools.String2Int64s(req.RoleIds)
	if len(roleIds) == 0 {
		return httpResp.ErrorCode(httpResp.ErrCodeInvalidParams)
	}
	return dao.Q.Transaction(func(tx *dao.Query) error {
		if _, e := tx.SysUserRole.Where(tx.SysUserRole.UserID.Eq(req.UserId)).Delete(); e != nil {
			return e
		}
		data := make([]*model.SysUserRole, 0)
		for _, v := range roleIds {
			data = append(data, &model.SysUserRole{
				UserID: req.UserId,
				RoleID: v,
			})
		}
		return tx.SysUserRole.CreateInBatches(data, 100)
	})
}

func (s *user) info(uid int64) (map[string]any, error) {
	var row struct {
		*model.SysUser
		Dept     *model.SysDept `json:"dept"`
		DeptName string         `json:"deptName,omitempty"`
		Leader   string         `json:"leader,omitempty"`
	}

	err := dao.SysUser.Select(dao.SysUser.ALL, dao.SysDept.DeptName, dao.SysDept.Leader).
		LeftJoin(dao.SysDept, dao.SysDept.DeptID.EqCol(dao.SysUser.DeptID)).
		Where(dao.SysUser.UserID.Eq(uid)).Scan(&row)
	if err != nil {
		return nil, err
	}
	if row.SysUser == nil {
		return nil, httpResp.ErrorCode(httpResp.ErrCodeUserNotExists)
	}
	data := xgorm.ModelToMapWithTag(row.SysUser, "json")
	data["dept"] = &model.SysDept{
		DeptID:   row.DeptID,
		DeptName: row.DeptName,
		Leader:   row.Leader,
	}
	// 获取角色
	roleRows, err := dao.SysRole.Distinct(dao.SysRole.ALL).LeftJoin(dao.SysUserRole, dao.SysUserRole.RoleID.EqCol(dao.SysRole.RoleID)).Order(dao.SysRole.RoleSort).Where(dao.SysUserRole.UserID.Eq(uid)).Find()
	if err != nil {
		return nil, err
	}
	data["roles"] = roleRows

	return data, nil
}

func (s *user) RoleAndPost(uid int64) (map[string]any, error) {
	data := make(map[string]any)
	postRow, err := dao.SysPost.Order(dao.SysPost.PostSort).Find()
	if err != nil {
		return nil, err
	}
	data["posts"] = postRow
	roleRow, err := dao.SysRole.Where(dao.SysRole.RoleID.Neq(constants.SuperAdminId)).Order(dao.SysRole.RoleSort).Find()
	if err != nil {
		return nil, err
	}
	data["roles"] = roleRow
	if uid > 0 && uid != constants.SuperAdminId {
		pids := make([]int64, 0)
		prows, err := dao.SysUserPost.Distinct(dao.SysUserPost.PostID).Where(dao.SysUserPost.UserID.Eq(uid)).Find()
		if err != nil {
			return nil, err
		}
		for _, v := range prows {
			pids = append(pids, v.PostID)
		}
		data["postIds"] = pids

		rids := make([]int64, 0)
		rrows, err := dao.SysUserRole.Distinct(dao.SysUserRole.RoleID).Where(dao.SysUserRole.UserID.Eq(uid)).Find()
		if err != nil {
			return nil, err
		}
		for _, v := range rrows {
			rids = append(rids, v.RoleID)
		}
		data["roleIds"] = rids

	}

	return data, nil
}

func (s *user) Delete(ctx *gin.Context) error {
	ids := commonTools.String2Int64s(ctx.Param("ids"))
	if len(ids) == 0 {
		return httpResp.ErrorCode(httpResp.ErrCodeInvalidParams)
	}

	if slices.Contains(ids, constants.SuperAdminId) {
		return httpResp.ErrorCode(http.StatusForbidden)
	}
	_, err := dao.SysUser.Where(dao.SysUser.UserID.In(ids...)).Delete()
	if err == nil {
		for _, v := range ids {
			s.LoginUserClean(v)
		}
	}
	return err
}

func (s *user) Add(ctx *gin.Context) error {
	var req entities.UserAddReq
	if err := ctx.ShouldBindJSON(&req); err != nil {
		return req.FormatErr(err)
	}
	if req.UserName == constants.SuperAdmin {
		return errors.Errorf("用户名称不可以是'%s'", req.UserName)
	}

	if count, _ := dao.SysUser.Where(dao.SysUser.UserName.Eq(req.UserName)).Count(); count > 0 {
		return errors.Errorf("新增用户失败，用户名称'%s'已存在", req.UserName)
	}
	if count, _ := dao.SysUser.Where(dao.SysUser.NickName.Eq(req.NickName)).Count(); count > 0 {
		return errors.Errorf("新增用户失败，用户昵称'%s'已存在", req.NickName)
	}
	if req.Email != "" {
		if count, _ := dao.SysUser.Where(dao.SysUser.Email.Eq(req.Email)).Count(); count > 0 {
			return errors.Errorf("新增用户失败，邮箱'%s'已存在", req.Email)
		}
	}

	if req.PhoneNumber != "" {
		if count, _ := dao.SysUser.Where(dao.SysUser.Phonenumber.Eq(req.PhoneNumber)).Count(); count > 0 {
			return errors.Errorf("新增用户失败，手机号'%s'已存在", req.PhoneNumber)
		}
	}
	if index := slices.Index(req.RoleIds, constants.SuperAdminId); index > -1 {
		req.RoleIds = slices.Delete(req.RoleIds, index, index+1)
	}

	userInfo := tools.GetUserInfo(ctx)
	row := &model.SysUser{
		DeptID:      req.DeptId,
		UserName:    req.UserName,
		NickName:    req.NickName,
		UserType:    "00",
		Email:       req.Email,
		Phonenumber: req.PhoneNumber,
		Sex:         req.Sex,
		Status:      req.Status,
		CreateBy:    userInfo.User.UserName,
		CreateTime:  xtime.Time(time.Now()),
		UpdateBy:    userInfo.User.UserName,
		UpdateTime:  xtime.Time(time.Now()),
		Remark:      req.Remark,
	}
	if row.Status == "" {
		row.Status = constants.OkStatus
	}
	// 密码加密
	password, err := xutils.PasswordHash(req.Password)
	if err != nil {
		return err
	}
	row.Password = password
	return dao.Q.Transaction(func(tx *dao.Query) error {
		if e := tx.SysUser.Create(row); e != nil {
			return e
		}
		return s.setRelation(tx, row.UserID, req.RoleIds, req.PostIds)
	})
}

func (s *user) Save(ctx *gin.Context) error {
	var req entities.UserSaveReq
	if err := ctx.ShouldBindJSON(&req); err != nil {
		return req.FormatErr(err)
	}
	if req.UserId == constants.SuperAdminId {
		return httpResp.ErrorCode(http.StatusForbidden)
	}
	// 校验参数
	conds := []gen.Condition{dao.SysUser.UserID.Neq(req.UserId), dao.SysUser.NickName.Eq(req.NickName)}
	if count, _ := dao.SysUser.Where(conds...).Count(); count > 0 {
		return errors.Errorf("修改用户失败，用户昵称'%s'已存在", req.NickName)
	}
	if req.Email != "" {
		conds[1] = dao.SysUser.Email.Eq(req.Email)
		if count, _ := dao.SysUser.Where(conds...).Count(); count > 0 {
			return errors.Errorf("修改用户失败，邮箱'%s'已存在", req.Email)
		}
	}
	if req.PhoneNumber != "" {
		conds[1] = dao.SysUser.Phonenumber.Eq(req.PhoneNumber)
		if count, _ := dao.SysUser.Where(conds...).Count(); count > 0 {
			return errors.Errorf("修改用户失败，手机号'%s'已存在", req.PhoneNumber)
		}
	}

	if index := slices.Index(req.RoleIds, constants.SuperAdminId); index > -1 {
		req.RoleIds = slices.Delete(req.RoleIds, index, index+1)
	}

	row, err := dao.SysUser.Where(dao.SysUser.UserID.Eq(req.UserId)).Take()
	if err != nil {
		return err
	}
	userInfo := tools.GetUserInfo(ctx)
	row.UpdateBy = userInfo.User.UserName
	row.UpdateTime = xtime.Time(time.Now())
	row.NickName = req.NickName
	row.DeptID = req.DeptId
	row.Email = req.Email
	row.Phonenumber = req.PhoneNumber
	row.Status = req.Status
	row.Sex = req.Sex
	row.Remark = req.Remark
	data := xgorm.ModelToMap(row)
	return dao.Q.Transaction(func(tx *dao.Query) error {
		if _, e := tx.SysUser.Where(tx.SysUser.UserID.Eq(req.UserId)).Updates(data); e != nil {
			return e
		}
		return s.setRelation(tx, req.UserId, req.RoleIds, req.PostIds)
	})
}

func (s *user) ChangeStatus(ctx *gin.Context) error {
	var req struct {
		UserId int64  `json:"userId"`
		Status string `json:"status"`
	}
	if err := ctx.ShouldBindJSON(&req); err != nil {
		return err
	}
	if req.UserId == constants.SuperAdminId {
		return httpResp.ErrorCode(http.StatusForbidden)
	}
	if req.UserId == 0 || req.Status == "" {
		return httpResp.ErrorCode(httpResp.ErrCodeInvalidParams)
	}

	userInfo := tools.GetUserInfo(ctx)
	row, err := dao.SysUser.Where(dao.SysUser.UserID.Eq(req.UserId)).Take()
	if err != nil {
		return err
	}
	row.UpdateBy = userInfo.User.UserName
	row.UpdateTime = xtime.Time(time.Now())
	row.Status = req.Status
	_, err = dao.SysUser.Where(dao.SysUser.UserID.Eq(req.UserId)).Updates(row)
	if err == nil {
		err = s.LoginUserClean(req.UserId)
	}
	return err
}

func (s *user) ResetPwd(ctx *gin.Context) error {
	var req struct {
		UserId   int64  `json:"userId"`
		Password string `json:"password"`
	}
	if err := ctx.ShouldBindJSON(&req); err != nil {
		return err
	}
	if req.UserId == constants.SuperAdminId {
		return httpResp.ErrorCode(http.StatusForbidden)
	}
	if req.UserId == 0 || req.Password == "" {
		return httpResp.ErrorCode(httpResp.ErrCodeInvalidParams)
	}

	if tools.PasswordCheck(req.Password) != nil {
		return constants.PasswordErr
	}
	password, err := xutils.PasswordHash(req.Password)
	if err != nil {
		return err
	}
	userInfo := tools.GetUserInfo(ctx)
	row, err := dao.SysUser.Where(dao.SysUser.UserID.Eq(req.UserId)).Take()
	if err != nil {
		return err
	}
	row.UpdateBy = userInfo.User.UserName
	row.UpdateTime = xtime.Time(time.Now())
	row.Password = password
	_, err = dao.SysUser.Where(dao.SysUser.UserID.Eq(req.UserId)).Updates(row)
	if err == nil {
		err = s.LoginUserClean(req.UserId)
	}
	return err
}

func (s *user) setRelation(tx *dao.Query, uid int64, roleIds []int64, postIds []int64) error {
	var err error
	if len(roleIds) > 0 {
		_, err = tx.SysUserRole.Where(dao.SysUserRole.UserID.Eq(uid)).Delete()
		if err != nil {
			return err
		}
		data := make([]*model.SysUserRole, 0)
		for _, v := range roleIds {
			data = append(data, &model.SysUserRole{
				UserID: uid,
				RoleID: v,
			})
		}
		err = tx.SysUserRole.CreateInBatches(data, 100)
		if err != nil {
			return err
		}
	}

	if len(postIds) > 0 {
		_, err = tx.SysUserPost.Where(dao.SysUserPost.UserID.Eq(uid)).Delete()
		if err != nil {
			return err
		}
		data := make([]*model.SysUserPost, 0)
		for _, v := range postIds {
			data = append(data, &model.SysUserPost{
				UserID: uid,
				PostID: v,
			})
		}
		err = tx.SysUserPost.CreateInBatches(data, 100)
		if err != nil {
			return err
		}
	}

	return err
}

func (s *user) formatListConds(req *entities.UserPageListReq) []gen.Condition {
	req.Page()
	conds := []gen.Condition{dao.SysUser.UserID.Neq(constants.SuperAdminId)}
	if req.UserName != "" {
		conds = append(conds, dao.SysUser.UserName.Like(`%`+req.UserName+`%`))
	}
	if req.PhoneNumber != "" {
		conds = append(conds, dao.SysUser.Phonenumber.Like(`%`+req.PhoneNumber+`%`))
	}
	if req.DeptId > 0 {
		conds = append(conds, dao.SysUser.DeptID.Eq(req.DeptId))
	}
	if req.Status != "" {
		conds = append(conds, dao.SysUser.Status.Eq(req.Status))
	}
	if !req.BT.IsZero() && !req.ET.IsZero() {
		conds = append(conds, dao.SysUser.CreateTime.Between(req.BT, req.ET))
	}

	return conds
}

func (s *user) DeptTree(ctx *gin.Context) ([]*entities.TreeItem, error) {
	userInfo := tools.GetUserInfo(ctx)
	if !userInfo.HasPermission("system:user:list") {
		return make([]*entities.TreeItem, 0), nil
	}
	return Dept.Tree("")
}
